// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/http/transport_security_state.h"

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/build_time.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "crypto/sha2.h"
#include "net/base/host_port_pair.h"
#include "net/cert/ct_policy_status.h"
#include "net/cert/x509_cert_types.h"
#include "net/cert/x509_certificate.h"
#include "net/dns/dns_util.h"
#include "net/http/http_security_headers.h"
#include "net/ssl/ssl_info.h"

#if !defined(OS_NACL)
#include "base/metrics/field_trial.h"
#endif

namespace net {

namespace {

#include "net/http/transport_security_state_ct_policies.inc"
#include "net/http/transport_security_state_static.h"

    const size_t kMaxHPKPReportCacheEntries = 50;
    const int kTimeToRememberHPKPReportsMins = 60;
    const size_t kReportCacheKeyLength = 16;

    // Override for ShouldRequireCT() for unit tests. Possible values:
    //  -1: Unless a delegate says otherwise, do not require CT.
    //   0: Use the default implementation (e.g. production)
    //   1: Unless a delegate says otherwise, require CT.
    int g_ct_required_for_testing = 0;

    // LessThan comparator for use with std::binary_search() in determining
    // whether a SHA-256 HashValue appears within a sorted array of
    // SHA256HashValues.
    struct SHA256ToHashValueComparator {
        bool operator()(const SHA256HashValue& lhs, const HashValue& rhs) const
        {
            DCHECK_EQ(HASH_VALUE_SHA256, rhs.tag);
            return memcmp(lhs.data, rhs.data(), rhs.size()) < 0;
        }

        bool operator()(const HashValue& lhs, const SHA256HashValue& rhs) const
        {
            DCHECK_EQ(HASH_VALUE_SHA256, lhs.tag);
            return memcmp(lhs.data(), rhs.data, lhs.size()) < 0;
        }
    };

    void RecordUMAForHPKPReportFailure(const GURL& report_uri, int net_error)
    {
        UMA_HISTOGRAM_SPARSE_SLOWLY("Net.PublicKeyPinReportSendingFailure",
            net_error);
    }

    std::string TimeToISO8601(const base::Time& t)
    {
        base::Time::Exploded exploded;
        t.UTCExplode(&exploded);
        return base::StringPrintf(
            "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
            exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
            exploded.millisecond);
    }

    std::unique_ptr<base::ListValue> GetPEMEncodedChainAsList(
        const net::X509Certificate* cert_chain)
    {
        if (!cert_chain)
            return base::WrapUnique(new base::ListValue());

        std::unique_ptr<base::ListValue> result(new base::ListValue());
        std::vector<std::string> pem_encoded_chain;
        cert_chain->GetPEMEncodedChain(&pem_encoded_chain);
        for (const std::string& cert : pem_encoded_chain)
            result->Append(base::WrapUnique(new base::StringValue(cert)));

        return result;
    }

    bool HashReportForCache(const base::DictionaryValue& report,
        const GURL& report_uri,
        std::string* cache_key)
    {
        char hashed[crypto::kSHA256Length];
        std::string to_hash;
        if (!base::JSONWriter::Write(report, &to_hash))
            return false;
        to_hash += "," + report_uri.spec();
        crypto::SHA256HashString(to_hash, hashed, sizeof(hashed));
        static_assert(kReportCacheKeyLength <= sizeof(hashed),
            "HPKP report cache key size is larger than hash size.");
        *cache_key = std::string(hashed, kReportCacheKeyLength);
        return true;
    }

    bool GetHPKPReport(const HostPortPair& host_port_pair,
        const TransportSecurityState::PKPState& pkp_state,
        const X509Certificate* served_certificate_chain,
        const X509Certificate* validated_certificate_chain,
        std::string* serialized_report,
        std::string* cache_key)
    {
        if (pkp_state.report_uri.is_empty())
            return false;

        base::DictionaryValue report;
        base::Time now = base::Time::Now();
        report.SetString("hostname", host_port_pair.host());
        report.SetInteger("port", host_port_pair.port());
        report.SetBoolean("include-subdomains", pkp_state.include_subdomains);
        report.SetString("noted-hostname", pkp_state.domain);

        std::unique_ptr<base::ListValue> served_certificate_chain_list = GetPEMEncodedChainAsList(served_certificate_chain);
        std::unique_ptr<base::ListValue> validated_certificate_chain_list = GetPEMEncodedChainAsList(validated_certificate_chain);
        report.Set("served-certificate-chain",
            std::move(served_certificate_chain_list));
        report.Set("validated-certificate-chain",
            std::move(validated_certificate_chain_list));

        std::unique_ptr<base::ListValue> known_pin_list(new base::ListValue());
        for (const auto& hash_value : pkp_state.spki_hashes) {
            std::string known_pin;

            switch (hash_value.tag) {
            case HASH_VALUE_SHA1:
                known_pin += "pin-sha1=";
                break;
            case HASH_VALUE_SHA256:
                known_pin += "pin-sha256=";
                break;
            }

            std::string base64_value;
            base::Base64Encode(
                base::StringPiece(reinterpret_cast<const char*>(hash_value.data()),
                    hash_value.size()),
                &base64_value);
            known_pin += "\"" + base64_value + "\"";

            known_pin_list->Append(
                std::unique_ptr<base::Value>(new base::StringValue(known_pin)));
        }

        report.Set("known-pins", std::move(known_pin_list));

        // For the sent reports cache, do not include the effective expiration
        // date. The expiration date will likely change every time the user
        // visits the site, so it would prevent reports from being effectively
        // deduplicated.
        if (!HashReportForCache(report, pkp_state.report_uri, cache_key)) {
            LOG(ERROR) << "Failed to compute cache key for HPKP violation report.";
            return false;
        }

        report.SetString("date-time", TimeToISO8601(now));
        report.SetString("effective-expiration-date",
            TimeToISO8601(pkp_state.expiry));
        if (!base::JSONWriter::Write(report, serialized_report)) {
            LOG(ERROR) << "Failed to serialize HPKP violation report.";
            return false;
        }

        return true;
    }

    // Do not send a report over HTTPS to the same host that set the
    // pin. Such report URIs will result in loops. (A.com has a pinning
    // violation which results in a report being sent to A.com, which
    // results in a pinning violation which results in a report being sent
    // to A.com, etc.)
    bool IsReportUriValidForHost(const GURL& report_uri, const std::string& host)
    {
        return (report_uri.host_piece() != host || !report_uri.SchemeIsCryptographic());
    }

    std::string HashesToBase64String(const HashValueVector& hashes)
    {
        std::string str;
        for (size_t i = 0; i != hashes.size(); ++i) {
            if (i != 0)
                str += ",";
            str += hashes[i].ToString();
        }
        return str;
    }

    std::string HashHost(const std::string& canonicalized_host)
    {
        char hashed[crypto::kSHA256Length];
        crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed));
        return std::string(hashed, sizeof(hashed));
    }

    // Returns true if the intersection of |a| and |b| is not empty. If either
    // |a| or |b| is empty, returns false.
    bool HashesIntersect(const HashValueVector& a,
        const HashValueVector& b)
    {
        for (const auto& hash : a) {
            auto p = std::find(b.begin(), b.end(), hash);
            if (p != b.end())
                return true;
        }
        return false;
    }

    bool AddHash(const char* sha256_hash, HashValueVector* out)
    {
        HashValue hash(HASH_VALUE_SHA256);
        memcpy(hash.data(), sha256_hash, hash.size());
        out->push_back(hash);
        return true;
    }

    // Converts |hostname| from dotted form ("www.google.com") to the form
    // used in DNS: "\x03www\x06google\x03com", lowercases that, and returns
    // the result.
    std::string CanonicalizeHost(const std::string& host)
    {
        // We cannot perform the operations as detailed in the spec here as |host|
        // has already undergone IDN processing before it reached us. Thus, we check
        // that there are no invalid characters in the host and lowercase the result.
        std::string new_host;
        if (!DNSDomainFromDot(host, &new_host)) {
            // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole
            // name is >255 bytes. However, search terms can have those properties.
            return std::string();
        }

        for (size_t i = 0; new_host[i]; i += new_host[i] + 1) {
            const unsigned label_length = static_cast<unsigned>(new_host[i]);
            if (!label_length)
                break;

            for (size_t j = 0; j < label_length; ++j) {
                new_host[i + 1 + j] = static_cast<char>(tolower(new_host[i + 1 + j]));
            }
        }

        return new_host;
    }

    // BitReader is a class that allows a bytestring to be read bit-by-bit.
    class BitReader {
    public:
        BitReader(const uint8_t* bytes, size_t num_bits)
            : bytes_(bytes)
            , num_bits_(num_bits)
            , num_bytes_((num_bits + 7) / 8)
            , current_byte_index_(0)
            , num_bits_used_(8)
        {
        }

        // Next sets |*out| to the next bit from the input. It returns false if no
        // more bits are available or true otherwise.
        bool Next(bool* out)
        {
            if (num_bits_used_ == 8) {
                if (current_byte_index_ >= num_bytes_) {
                    return false;
                }
                current_byte_ = bytes_[current_byte_index_++];
                num_bits_used_ = 0;
            }

            *out = 1 & (current_byte_ >> (7 - num_bits_used_));
            num_bits_used_++;
            return true;
        }

        // Read sets the |num_bits| least-significant bits of |*out| to the value of
        // the next |num_bits| bits from the input. It returns false if there are
        // insufficient bits in the input or true otherwise.
        bool Read(unsigned num_bits, uint32_t* out)
        {
            DCHECK_LE(num_bits, 32u);

            uint32_t ret = 0;
            for (unsigned i = 0; i < num_bits; ++i) {
                bool bit;
                if (!Next(&bit)) {
                    return false;
                }
                ret |= static_cast<uint32_t>(bit) << (num_bits - 1 - i);
            }

            *out = ret;
            return true;
        }

        // Unary sets |*out| to the result of decoding a unary value from the input.
        // It returns false if there were insufficient bits in the input and true
        // otherwise.
        bool Unary(size_t* out)
        {
            size_t ret = 0;

            for (;;) {
                bool bit;
                if (!Next(&bit)) {
                    return false;
                }
                if (!bit) {
                    break;
                }
                ret++;
            }

            *out = ret;
            return true;
        }

        // Seek sets the current offest in the input to bit number |offset|. It
        // returns true if |offset| is within the range of the input and false
        // otherwise.
        bool Seek(size_t offset)
        {
            if (offset >= num_bits_) {
                return false;
            }
            current_byte_index_ = offset / 8;
            current_byte_ = bytes_[current_byte_index_++];
            num_bits_used_ = offset % 8;
            return true;
        }

    private:
        const uint8_t* const bytes_;
        const size_t num_bits_;
        const size_t num_bytes_;
        // current_byte_index_ contains the current byte offset in |bytes_|.
        size_t current_byte_index_;
        // current_byte_ contains the current byte of the input.
        uint8_t current_byte_;
        // num_bits_used_ contains the number of bits of |current_byte_| that have
        // been read.
        unsigned num_bits_used_;
    };

    // HuffmanDecoder is a very simple Huffman reader. The input Huffman tree is
    // simply encoded as a series of two-byte structures. The first byte determines
    // the "0" pointer for that node and the second the "1" pointer. Each byte
    // either has the MSB set, in which case the bottom 7 bits are the value for
    // that position, or else the bottom seven bits contain the index of a node.
    //
    // The tree is decoded by walking rather than a table-driven approach.
    class HuffmanDecoder {
    public:
        HuffmanDecoder(const uint8_t* tree, size_t tree_bytes)
            : tree_(tree)
            , tree_bytes_(tree_bytes)
        {
        }

        bool Decode(BitReader* reader, char* out)
        {
            const uint8_t* current = &tree_[tree_bytes_ - 2];

            for (;;) {
                bool bit;
                if (!reader->Next(&bit)) {
                    return false;
                }

                uint8_t b = current[bit];
                if (b & 0x80) {
                    *out = static_cast<char>(b & 0x7f);
                    return true;
                }

                unsigned offset = static_cast<unsigned>(b) * 2;
                DCHECK_LT(offset, tree_bytes_);
                if (offset >= tree_bytes_) {
                    return false;
                }

                current = &tree_[offset];
            }
        }

    private:
        const uint8_t* const tree_;
        const size_t tree_bytes_;
    };

    // PreloadResult is the result of resolving a specific name in the preloaded
    // data.
    struct PreloadResult {
        uint32_t pinset_id;
        uint32_t domain_id;
        // hostname_offset contains the number of bytes from the start of the given
        // hostname where the name of the matching entry starts.
        size_t hostname_offset;
        bool sts_include_subdomains;
        bool pkp_include_subdomains;
        bool force_https;
        bool has_pins;
        bool expect_ct;
        uint32_t expect_ct_report_uri_id;
        bool expect_staple;
        bool expect_staple_include_subdomains;
        uint32_t expect_staple_report_uri_id;
    };

    // DecodeHSTSPreloadRaw resolves |hostname| in the preloaded data. It returns
    // false on internal error and true otherwise. After a successful return,
    // |*out_found| is true iff a relevant entry has been found. If so, |*out|
    // contains the details.
    //
    // Don't call this function, call DecodeHSTSPreload, below.
    //
    // Although this code should be robust, it never processes attacker-controlled
    // data -- it only operates on the preloaded data built into the binary.
    //
    // The preloaded data is represented as a trie and matches the hostname
    // backwards. Each node in the trie starts with a number of characters, which
    // must match exactly. After that is a dispatch table which maps the next
    // character in the hostname to another node in the trie.
    //
    // In the dispatch table, the zero character represents the "end of string"
    // (which is the *beginning* of a hostname since we process it backwards). The
    // value in that case is special -- rather than an offset to another trie node,
    // it contains the HSTS information: whether subdomains are included, pinsets
    // etc. If an "end of string" matches a period in the hostname then the
    // information is remembered because, if no more specific node is found, then
    // that information applies to the hostname.
    //
    // Dispatch tables are always given in order, but the "end of string" (zero)
    // value always comes before an entry for '.'.
    bool DecodeHSTSPreloadRaw(const std::string& search_hostname,
        bool* out_found,
        PreloadResult* out)
    {
        HuffmanDecoder huffman(kHSTSHuffmanTree, sizeof(kHSTSHuffmanTree));
        BitReader reader(kPreloadedHSTSData, kPreloadedHSTSBits);
        size_t bit_offset = kHSTSRootPosition;
        static const char kEndOfString = 0;
        static const char kEndOfTable = 127;

        *out_found = false;

        // Ensure that |search_hostname| is a valid hostname before
        // processing.
        if (CanonicalizeHost(search_hostname).empty()) {
            return true;
        }

        // Normalize any trailing '.' used for DNS suffix searches.
        std::string hostname = search_hostname;
        size_t found = hostname.find_last_not_of('.');
        if (found != std::string::npos) {
            hostname.erase(found + 1);
        } else {
            hostname.clear();
        }

        // |hostname| has already undergone IDN conversion, so should be
        // entirely A-Labels. The preload data is entirely normalized to
        // lower case.
        hostname = base::ToLowerASCII(hostname);
        if (hostname.empty()) {
            return true;
        }

        // hostname_offset contains one more than the index of the current character
        // in the hostname that is being considered. It's one greater so that we can
        // represent the position just before the beginning (with zero).
        size_t hostname_offset = hostname.size();

        for (;;) {
            // Seek to the desired location.
            if (!reader.Seek(bit_offset)) {
                return false;
            }

            // Decode the unary length of the common prefix.
            size_t prefix_length;
            if (!reader.Unary(&prefix_length)) {
                return false;
            }

            // Match each character in the prefix.
            for (size_t i = 0; i < prefix_length; ++i) {
                if (hostname_offset == 0) {
                    // We can't match the terminator with a prefix string.
                    return true;
                }

                char c;
                if (!huffman.Decode(&reader, &c)) {
                    return false;
                }
                if (hostname[hostname_offset - 1] != c) {
                    return true;
                }
                hostname_offset--;
            }

            bool is_first_offset = true;
            size_t current_offset = 0;

            // Next is the dispatch table.
            for (;;) {
                char c;
                if (!huffman.Decode(&reader, &c)) {
                    return false;
                }
                if (c == kEndOfTable) {
                    // No exact match.
                    return true;
                }

                if (c == kEndOfString) {
                    PreloadResult tmp;
                    if (!reader.Next(&tmp.sts_include_subdomains) || !reader.Next(&tmp.force_https) || !reader.Next(&tmp.has_pins)) {
                        return false;
                    }

                    tmp.pkp_include_subdomains = tmp.sts_include_subdomains;

                    if (tmp.has_pins) {
                        if (!reader.Read(4, &tmp.pinset_id) || !reader.Read(9, &tmp.domain_id) || (!tmp.sts_include_subdomains && !reader.Next(&tmp.pkp_include_subdomains))) {
                            return false;
                        }
                    }

                    if (!reader.Next(&tmp.expect_ct))
                        return false;

                    if (tmp.expect_ct) {
                        if (!reader.Read(4, &tmp.expect_ct_report_uri_id))
                            return false;
                    }

                    if (!reader.Next(&tmp.expect_staple))
                        return false;
                    tmp.expect_staple_include_subdomains = false;
                    if (tmp.expect_staple) {
                        if (!reader.Next(&tmp.expect_staple_include_subdomains))
                            return false;
                        if (!reader.Read(4, &tmp.expect_staple_report_uri_id))
                            return false;
                    }

                    tmp.hostname_offset = hostname_offset;

                    if (hostname_offset == 0 || hostname[hostname_offset - 1] == '.') {
                        *out_found = tmp.sts_include_subdomains || tmp.pkp_include_subdomains || tmp.expect_staple_include_subdomains;
                        *out = tmp;

                        if (hostname_offset > 0) {
                            out->force_https &= tmp.sts_include_subdomains;
                        } else {
                            *out_found = true;
                            return true;
                        }
                    }

                    continue;
                }

                // The entries in a dispatch table are in order thus we can tell if there
                // will be no match if the current character past the one that we want.
                if (hostname_offset == 0 || hostname[hostname_offset - 1] < c) {
                    return true;
                }

                if (is_first_offset) {
                    // The first offset is backwards from the current position.
                    uint32_t jump_delta_bits;
                    uint32_t jump_delta;
                    if (!reader.Read(5, &jump_delta_bits) || !reader.Read(jump_delta_bits, &jump_delta)) {
                        return false;
                    }

                    if (bit_offset < jump_delta) {
                        return false;
                    }

                    current_offset = bit_offset - jump_delta;
                    is_first_offset = false;
                } else {
                    // Subsequent offsets are forward from the target of the first offset.
                    uint32_t is_long_jump;
                    if (!reader.Read(1, &is_long_jump)) {
                        return false;
                    }

                    uint32_t jump_delta;
                    if (!is_long_jump) {
                        if (!reader.Read(7, &jump_delta)) {
                            return false;
                        }
                    } else {
                        uint32_t jump_delta_bits;
                        if (!reader.Read(4, &jump_delta_bits) || !reader.Read(jump_delta_bits + 8, &jump_delta)) {
                            return false;
                        }
                    }

                    current_offset += jump_delta;
                    if (current_offset >= bit_offset) {
                        return false;
                    }
                }

                DCHECK_LT(0u, hostname_offset);
                if (hostname[hostname_offset - 1] == c) {
                    bit_offset = current_offset;
                    hostname_offset--;
                    break;
                }
            }
        }
    }

    bool DecodeHSTSPreload(const std::string& hostname, PreloadResult* out)
    {
        bool found;
        if (!DecodeHSTSPreloadRaw(hostname, &found, out)) {
            DCHECK(false) << "Internal error in DecodeHSTSPreloadRaw for hostname "
                          << hostname;
            return false;
        }

        return found;
    }

} // namespace

TransportSecurityState::TransportSecurityState()
    : enable_static_pins_(true)
    , enable_static_expect_ct_(true)
    , enable_static_expect_staple_(false)
    , sent_reports_cache_(kMaxHPKPReportCacheEntries)
{
// Static pinning is only enabled for official builds to make sure that
// others don't end up with pins that cannot be easily updated.
#if !defined(OFFICIAL_BUILD) || defined(OS_ANDROID) || defined(OS_IOS)
    enable_static_pins_ = false;
    enable_static_expect_ct_ = false;
#endif
    DCHECK(CalledOnValidThread());
}

// Both HSTS and HPKP cause fatal SSL errors, so return true if a
// host has either.
bool TransportSecurityState::ShouldSSLErrorsBeFatal(const std::string& host)
{
    STSState sts_state;
    PKPState pkp_state;
    if (GetStaticDomainState(host, &sts_state, &pkp_state))
        return true;
    if (GetDynamicSTSState(host, &sts_state))
        return true;
    return GetDynamicPKPState(host, &pkp_state);
}

bool TransportSecurityState::ShouldUpgradeToSSL(const std::string& host)
{
    STSState dynamic_sts_state;
    if (GetDynamicSTSState(host, &dynamic_sts_state))
        return dynamic_sts_state.ShouldUpgradeToSSL();

    STSState static_sts_state;
    PKPState unused;
    if (GetStaticDomainState(host, &static_sts_state, &unused) && static_sts_state.ShouldUpgradeToSSL()) {
        return true;
    }

    return false;
}

TransportSecurityState::PKPStatus TransportSecurityState::CheckPublicKeyPins(
    const HostPortPair& host_port_pair,
    bool is_issued_by_known_root,
    const HashValueVector& public_key_hashes,
    const X509Certificate* served_certificate_chain,
    const X509Certificate* validated_certificate_chain,
    const PublicKeyPinReportStatus report_status,
    std::string* pinning_failure_log)
{
    // Perform pin validation only if the server actually has public key pins.
    if (!HasPublicKeyPins(host_port_pair.host())) {
        return PKPStatus::OK;
    }

    PKPStatus pin_validity = CheckPublicKeyPinsImpl(
        host_port_pair, is_issued_by_known_root, public_key_hashes,
        served_certificate_chain, validated_certificate_chain, report_status,
        pinning_failure_log);

    // Don't track statistics when a local trust anchor would override the pinning
    // anyway.
    if (!is_issued_by_known_root)
        return pin_validity;

    if (pin_validity == PKPStatus::VIOLATED) {
        LOG(ERROR) << *pinning_failure_log;
        ReportUMAOnPinFailure(host_port_pair.host());
    }
    UMA_HISTOGRAM_BOOLEAN("Net.PublicKeyPinSuccess",
        pin_validity == PKPStatus::OK);
    return pin_validity;
}

bool TransportSecurityState::HasPublicKeyPins(const std::string& host)
{
    PKPState dynamic_state;
    if (GetDynamicPKPState(host, &dynamic_state))
        return dynamic_state.HasPublicKeyPins();

    STSState unused;
    PKPState static_pkp_state;
    if (GetStaticDomainState(host, &unused, &static_pkp_state)) {
        if (static_pkp_state.HasPublicKeyPins())
            return true;
    }

    return false;
}

bool TransportSecurityState::ShouldRequireCT(
    const std::string& hostname,
    const X509Certificate* validated_certificate_chain,
    const HashValueVector& public_key_hashes)
{
    using CTRequirementLevel = RequireCTDelegate::CTRequirementLevel;

    CTRequirementLevel ct_required = CTRequirementLevel::DEFAULT;
    if (require_ct_delegate_)
        ct_required = require_ct_delegate_->IsCTRequiredForHost(hostname);
    if (ct_required != CTRequirementLevel::DEFAULT)
        return ct_required == CTRequirementLevel::REQUIRED;

    // Allow unittests to override the default result.
    if (g_ct_required_for_testing)
        return g_ct_required_for_testing == 1;

    // Until CT is required for all secure hosts on the Internet, this should
    // remain false. It is provided to simplify the various short-circuit
    // returns below.
    bool default_response = false;

// FieldTrials are not supported in Native Client apps.
#if !defined(OS_NACL)
    // Emergency escape valve; not to be activated until there's an actual
    // emergency (e.g. a weird path-building bug due to a CA's failed
    // disclosure of cross-signed sub-CAs).
    std::string group_name = base::FieldTrialList::FindFullName("EnforceCTForProblematicRoots");
    if (base::StartsWith(group_name, "disabled",
            base::CompareCase::INSENSITIVE_ASCII)) {
        return default_response;
    }
#endif

    const base::Time epoch = base::Time::UnixEpoch();
    for (const auto& restricted_ca : kCTRequiredPolicies) {
        if (epoch + restricted_ca.effective_date > validated_certificate_chain->valid_start()) {
            // The candidate cert is not subject to the CT policy, because it
            // was issued before the effective CT date.
            continue;
        }

        for (const auto& hash : public_key_hashes) {
            if (hash.tag != HASH_VALUE_SHA256)
                continue;

            // Determine if |hash| is in the set of roots of |restricted_ca|.
            if (!std::binary_search(restricted_ca.roots,
                    restricted_ca.roots + restricted_ca.roots_length,
                    hash, SHA256ToHashValueComparator())) {
                continue;
            }

            // Found a match, indicating this certificate is potentially
            // restricted. Determine if any of the hashes are on the exclusion
            // list as exempt from the CT requirement.
            for (const auto& sub_ca_hash : public_key_hashes) {
                if (sub_ca_hash.tag != HASH_VALUE_SHA256)
                    continue;
                if (std::binary_search(
                        restricted_ca.exceptions,
                        restricted_ca.exceptions + restricted_ca.exceptions_length,
                        sub_ca_hash, SHA256ToHashValueComparator())) {
                    // Found an excluded sub-CA; CT is not required.
                    return default_response;
                }
            }

            // No exception found. This certificate must conform to the CT policy.
            return true;
        }
    }

    return default_response;
}

void TransportSecurityState::SetDelegate(
    TransportSecurityState::Delegate* delegate)
{
    DCHECK(CalledOnValidThread());
    delegate_ = delegate;
}

void TransportSecurityState::SetReportSender(
    TransportSecurityState::ReportSenderInterface* report_sender)
{
    DCHECK(CalledOnValidThread());
    report_sender_ = report_sender;
    if (report_sender_)
        report_sender_->SetErrorCallback(base::Bind(RecordUMAForHPKPReportFailure));
}

void TransportSecurityState::SetExpectCTReporter(
    ExpectCTReporter* expect_ct_reporter)
{
    DCHECK(CalledOnValidThread());
    expect_ct_reporter_ = expect_ct_reporter;
}

void TransportSecurityState::SetRequireCTDelegate(RequireCTDelegate* delegate)
{
    DCHECK(CalledOnValidThread());
    require_ct_delegate_ = delegate;
}

void TransportSecurityState::AddHSTSInternal(
    const std::string& host,
    TransportSecurityState::STSState::UpgradeMode upgrade_mode,
    const base::Time& expiry,
    bool include_subdomains)
{
    DCHECK(CalledOnValidThread());

    STSState sts_state;
    sts_state.last_observed = base::Time::Now();
    sts_state.include_subdomains = include_subdomains;
    sts_state.expiry = expiry;
    sts_state.upgrade_mode = upgrade_mode;

    EnableSTSHost(host, sts_state);
}

void TransportSecurityState::AddHPKPInternal(const std::string& host,
    const base::Time& last_observed,
    const base::Time& expiry,
    bool include_subdomains,
    const HashValueVector& hashes,
    const GURL& report_uri)
{
    DCHECK(CalledOnValidThread());

    PKPState pkp_state;
    pkp_state.last_observed = last_observed;
    pkp_state.expiry = expiry;
    pkp_state.include_subdomains = include_subdomains;
    pkp_state.spki_hashes = hashes;
    pkp_state.report_uri = report_uri;

    EnablePKPHost(host, pkp_state);
}

void TransportSecurityState::EnableSTSHost(const std::string& host,
    const STSState& state)
{
    DCHECK(CalledOnValidThread());

    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
        return;

    // Only store new state when HSTS is explicitly enabled. If it is
    // disabled, remove the state from the enabled hosts.
    if (state.ShouldUpgradeToSSL()) {
        STSState sts_state(state);
        // No need to store this value since it is redundant. (|canonicalized_host|
        // is the map key.)
        sts_state.domain.clear();

        enabled_sts_hosts_[HashHost(canonicalized_host)] = sts_state;
    } else {
        const std::string hashed_host = HashHost(canonicalized_host);
        enabled_sts_hosts_.erase(hashed_host);
    }

    DirtyNotify();
}

void TransportSecurityState::EnablePKPHost(const std::string& host,
    const PKPState& state)
{
    DCHECK(CalledOnValidThread());

    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
        return;

    // Only store new state when HPKP is explicitly enabled. If it is
    // disabled, remove the state from the enabled hosts.
    if (state.HasPublicKeyPins()) {
        PKPState pkp_state(state);
        // No need to store this value since it is redundant. (|canonicalized_host|
        // is the map key.)
        pkp_state.domain.clear();

        enabled_pkp_hosts_[HashHost(canonicalized_host)] = pkp_state;
    } else {
        const std::string hashed_host = HashHost(canonicalized_host);
        enabled_pkp_hosts_.erase(hashed_host);
    }

    DirtyNotify();
}

TransportSecurityState::PKPStatus
TransportSecurityState::CheckPinsAndMaybeSendReport(
    const HostPortPair& host_port_pair,
    bool is_issued_by_known_root,
    const TransportSecurityState::PKPState& pkp_state,
    const HashValueVector& hashes,
    const X509Certificate* served_certificate_chain,
    const X509Certificate* validated_certificate_chain,
    const TransportSecurityState::PublicKeyPinReportStatus report_status,
    std::string* failure_log)
{
    if (pkp_state.CheckPublicKeyPins(hashes, failure_log))
        return PKPStatus::OK;

    // Don't report violations for certificates that chain to local roots.
    if (!is_issued_by_known_root)
        return PKPStatus::BYPASSED;

    if (!report_sender_ || report_status != TransportSecurityState::ENABLE_PIN_REPORTS || pkp_state.report_uri.is_empty()) {
        return PKPStatus::VIOLATED;
    }

    DCHECK(pkp_state.report_uri.is_valid());
    // Report URIs should not be used if they are the same host as the pin
    // and are HTTPS, to avoid going into a report-sending loop.
    if (!IsReportUriValidForHost(pkp_state.report_uri, host_port_pair.host()))
        return PKPStatus::VIOLATED;

    std::string serialized_report;
    std::string report_cache_key;
    if (!GetHPKPReport(host_port_pair, pkp_state, served_certificate_chain,
            validated_certificate_chain, &serialized_report,
            &report_cache_key)) {
        return PKPStatus::VIOLATED;
    }

    // Limit the rate at which duplicate reports are sent to the same
    // report URI. The same report will not be sent within
    // |kTimeToRememberHPKPReportsMins|, which reduces load on servers and
    // also prevents accidental loops (a.com triggers a report to b.com
    // which triggers a report to a.com). See section 2.1.4 of RFC 7469.
    if (sent_reports_cache_.Get(report_cache_key, base::TimeTicks::Now()))
        return PKPStatus::VIOLATED;
    sent_reports_cache_.Put(
        report_cache_key, true, base::TimeTicks::Now(),
        base::TimeTicks::Now() + base::TimeDelta::FromMinutes(kTimeToRememberHPKPReportsMins));

    report_sender_->Send(pkp_state.report_uri, serialized_report);
    return PKPStatus::VIOLATED;
}

bool TransportSecurityState::GetStaticExpectCTState(
    const std::string& host,
    ExpectCTState* expect_ct_state) const
{
    DCHECK(CalledOnValidThread());

    if (!IsBuildTimely())
        return false;

    PreloadResult result;
    if (!DecodeHSTSPreload(host, &result))
        return false;

    if (!enable_static_expect_ct_ || !result.expect_ct)
        return false;

    expect_ct_state->domain = host.substr(result.hostname_offset);
    expect_ct_state->report_uri = GURL(kExpectCTReportURIs[result.expect_ct_report_uri_id]);
    return true;
}

bool TransportSecurityState::GetStaticExpectStapleState(
    const std::string& host,
    ExpectStapleState* expect_staple_state) const
{
    DCHECK(CalledOnValidThread());

    if (!IsBuildTimely())
        return false;

    PreloadResult result;
    if (!DecodeHSTSPreload(host, &result))
        return false;

    if (!enable_static_expect_staple_ || !result.expect_staple)
        return false;

    expect_staple_state->domain = host.substr(result.hostname_offset);
    expect_staple_state->include_subdomains = result.expect_staple_include_subdomains;
    expect_staple_state->report_uri = GURL(kExpectStapleReportURIs[result.expect_staple_report_uri_id]);
    return true;
}

bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host)
{
    DCHECK(CalledOnValidThread());

    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
        return false;

    const std::string hashed_host = HashHost(canonicalized_host);
    bool deleted = false;
    STSStateMap::iterator sts_interator = enabled_sts_hosts_.find(hashed_host);
    if (sts_interator != enabled_sts_hosts_.end()) {
        enabled_sts_hosts_.erase(sts_interator);
        deleted = true;
    }

    PKPStateMap::iterator pkp_iterator = enabled_pkp_hosts_.find(hashed_host);
    if (pkp_iterator != enabled_pkp_hosts_.end()) {
        enabled_pkp_hosts_.erase(pkp_iterator);
        deleted = true;
    }

    if (deleted)
        DirtyNotify();
    return deleted;
}

void TransportSecurityState::ClearDynamicData()
{
    DCHECK(CalledOnValidThread());
    enabled_sts_hosts_.clear();
    enabled_pkp_hosts_.clear();
}

void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time)
{
    DCHECK(CalledOnValidThread());

    bool dirtied = false;
    STSStateMap::iterator sts_iterator = enabled_sts_hosts_.begin();
    while (sts_iterator != enabled_sts_hosts_.end()) {
        if (sts_iterator->second.last_observed >= time) {
            dirtied = true;
            enabled_sts_hosts_.erase(sts_iterator++);
            continue;
        }

        ++sts_iterator;
    }

    PKPStateMap::iterator pkp_iterator = enabled_pkp_hosts_.begin();
    while (pkp_iterator != enabled_pkp_hosts_.end()) {
        if (pkp_iterator->second.last_observed >= time) {
            dirtied = true;
            enabled_pkp_hosts_.erase(pkp_iterator++);
            continue;
        }

        ++pkp_iterator;
    }

    if (dirtied)
        DirtyNotify();
}

TransportSecurityState::~TransportSecurityState()
{
    DCHECK(CalledOnValidThread());
}

void TransportSecurityState::DirtyNotify()
{
    DCHECK(CalledOnValidThread());

    if (delegate_)
        delegate_->StateIsDirty(this);
}

bool TransportSecurityState::AddHSTSHeader(const std::string& host,
    const std::string& value)
{
    DCHECK(CalledOnValidThread());

    base::Time now = base::Time::Now();
    base::TimeDelta max_age;
    bool include_subdomains;
    if (!ParseHSTSHeader(value, &max_age, &include_subdomains)) {
        return false;
    }

    // Handle max-age == 0.
    STSState::UpgradeMode upgrade_mode;
    if (max_age.InSeconds() == 0) {
        upgrade_mode = STSState::MODE_DEFAULT;
    } else {
        upgrade_mode = STSState::MODE_FORCE_HTTPS;
    }

    AddHSTSInternal(host, upgrade_mode, now + max_age, include_subdomains);
    return true;
}

bool TransportSecurityState::AddHPKPHeader(const std::string& host,
    const std::string& value,
    const SSLInfo& ssl_info)
{
    DCHECK(CalledOnValidThread());

    base::Time now = base::Time::Now();
    base::TimeDelta max_age;
    bool include_subdomains;
    HashValueVector spki_hashes;
    GURL report_uri;

    if (!ParseHPKPHeader(value, ssl_info.public_key_hashes, &max_age,
            &include_subdomains, &spki_hashes, &report_uri)) {
        return false;
    }
    // Handle max-age == 0.
    if (max_age.InSeconds() == 0)
        spki_hashes.clear();
    AddHPKPInternal(host, now, now + max_age, include_subdomains, spki_hashes,
        report_uri);
    return true;
}

void TransportSecurityState::AddHSTS(const std::string& host,
    const base::Time& expiry,
    bool include_subdomains)
{
    DCHECK(CalledOnValidThread());
    AddHSTSInternal(host, STSState::MODE_FORCE_HTTPS, expiry, include_subdomains);
}

void TransportSecurityState::AddHPKP(const std::string& host,
    const base::Time& expiry,
    bool include_subdomains,
    const HashValueVector& hashes,
    const GURL& report_uri)
{
    DCHECK(CalledOnValidThread());
    AddHPKPInternal(host, base::Time::Now(), expiry, include_subdomains, hashes,
        report_uri);
}

bool TransportSecurityState::ProcessHPKPReportOnlyHeader(
    const std::string& value,
    const HostPortPair& host_port_pair,
    const SSLInfo& ssl_info)
{
    DCHECK(CalledOnValidThread());

    base::Time now = base::Time::Now();
    bool include_subdomains;
    HashValueVector spki_hashes;
    GURL report_uri;
    std::string unused_failure_log;

    if (!ParseHPKPReportOnlyHeader(value, &include_subdomains, &spki_hashes,
            &report_uri)
        || !report_uri.is_valid() || report_uri.is_empty()) {
        return false;
    }

    PKPState pkp_state;
    pkp_state.last_observed = now;
    pkp_state.expiry = now;
    pkp_state.include_subdomains = include_subdomains;
    pkp_state.spki_hashes = spki_hashes;
    pkp_state.report_uri = report_uri;
    pkp_state.domain = DNSDomainToString(CanonicalizeHost(host_port_pair.host()));

    CheckPinsAndMaybeSendReport(
        host_port_pair, ssl_info.is_issued_by_known_root, pkp_state,
        ssl_info.public_key_hashes, ssl_info.unverified_cert.get(),
        ssl_info.cert.get(), ENABLE_PIN_REPORTS, &unused_failure_log);
    return true;
}

void TransportSecurityState::ProcessExpectCTHeader(
    const std::string& value,
    const HostPortPair& host_port_pair,
    const SSLInfo& ssl_info)
{
    DCHECK(CalledOnValidThread());

    if (!expect_ct_reporter_)
        return;

    if (value != "preload")
        return;

    if (!IsBuildTimely())
        return;

    if (!ssl_info.is_issued_by_known_root || !ssl_info.ct_compliance_details_available || ssl_info.ct_cert_policy_compliance == ct::CertPolicyCompliance::CERT_POLICY_COMPLIES_VIA_SCTS) {
        return;
    }

    ExpectCTState state;
    if (!GetStaticExpectCTState(host_port_pair.host(), &state))
        return;

    expect_ct_reporter_->OnExpectCTFailed(host_port_pair, state.report_uri,
        ssl_info);
}

// static
void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host)
{
    PreloadResult result;
    if (!DecodeHSTSPreload(host, &result) || !result.has_pins) {
        return;
    }

    DCHECK(result.domain_id != DOMAIN_NOT_PINNED);

    UMA_HISTOGRAM_SPARSE_SLOWLY(
        "Net.PublicKeyPinFailureDomain", result.domain_id);
}

// static
void TransportSecurityState::SetShouldRequireCTForTesting(bool* required)
{
    if (!required) {
        g_ct_required_for_testing = 0;
        return;
    }
    g_ct_required_for_testing = *required ? 1 : -1;
}

// static
bool TransportSecurityState::IsBuildTimely()
{
    const base::Time build_time = base::GetBuildTime();
    // We consider built-in information to be timely for 10 weeks.
    return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */;
}

TransportSecurityState::PKPStatus
TransportSecurityState::CheckPublicKeyPinsImpl(
    const HostPortPair& host_port_pair,
    bool is_issued_by_known_root,
    const HashValueVector& hashes,
    const X509Certificate* served_certificate_chain,
    const X509Certificate* validated_certificate_chain,
    const PublicKeyPinReportStatus report_status,
    std::string* failure_log)
{
    PKPState pkp_state;
    STSState unused;

    bool found_state = GetDynamicPKPState(host_port_pair.host(), &pkp_state) || GetStaticDomainState(host_port_pair.host(), &unused, &pkp_state);

    // HasPublicKeyPins should have returned true in order for this method to have
    // been called.
    DCHECK(found_state);
    return CheckPinsAndMaybeSendReport(
        host_port_pair, is_issued_by_known_root, pkp_state, hashes,
        served_certificate_chain, validated_certificate_chain, report_status,
        failure_log);
}

bool TransportSecurityState::GetStaticDomainState(const std::string& host,
    STSState* sts_state,
    PKPState* pkp_state) const
{
    DCHECK(CalledOnValidThread());

    sts_state->upgrade_mode = STSState::MODE_FORCE_HTTPS;
    sts_state->include_subdomains = false;
    pkp_state->include_subdomains = false;

    if (!IsBuildTimely())
        return false;

    PreloadResult result;
    if (!DecodeHSTSPreload(host, &result))
        return false;

    sts_state->domain = host.substr(result.hostname_offset);
    pkp_state->domain = sts_state->domain;
    sts_state->include_subdomains = result.sts_include_subdomains;
    sts_state->last_observed = base::GetBuildTime();
    sts_state->upgrade_mode = STSState::MODE_DEFAULT;
    if (result.force_https) {
        sts_state->upgrade_mode = STSState::MODE_FORCE_HTTPS;
    }

    if (enable_static_pins_ && result.has_pins) {
        pkp_state->include_subdomains = result.pkp_include_subdomains;
        pkp_state->last_observed = base::GetBuildTime();

        if (result.pinset_id >= arraysize(kPinsets))
            return false;
        const Pinset* pinset = &kPinsets[result.pinset_id];

        if (pinset->report_uri != kNoReportURI)
            pkp_state->report_uri = GURL(pinset->report_uri);

        if (pinset->accepted_pins) {
            const char* const* sha256_hash = pinset->accepted_pins;
            while (*sha256_hash) {
                AddHash(*sha256_hash, &pkp_state->spki_hashes);
                sha256_hash++;
            }
        }
        if (pinset->rejected_pins) {
            const char* const* sha256_hash = pinset->rejected_pins;
            while (*sha256_hash) {
                AddHash(*sha256_hash, &pkp_state->bad_spki_hashes);
                sha256_hash++;
            }
        }
    }

    return true;
}

bool TransportSecurityState::IsGooglePinnedHost(const std::string& host) const
{
    DCHECK(CalledOnValidThread());

    if (!IsBuildTimely())
        return false;

    PreloadResult result;
    if (!DecodeHSTSPreload(host, &result))
        return false;

    if (!result.has_pins)
        return false;

    if (result.pinset_id >= arraysize(kPinsets))
        return false;

    return kPinsets[result.pinset_id].accepted_pins == kGoogleAcceptableCerts;
}

bool TransportSecurityState::GetDynamicSTSState(const std::string& host,
    STSState* result)
{
    DCHECK(CalledOnValidThread());

    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
        return false;

    base::Time current_time(base::Time::Now());

    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
        std::string host_sub_chunk(&canonicalized_host[i],
            canonicalized_host.size() - i);
        STSStateMap::iterator j = enabled_sts_hosts_.find(HashHost(host_sub_chunk));
        if (j == enabled_sts_hosts_.end())
            continue;

        // If the entry is invalid, drop it.
        if (current_time > j->second.expiry) {
            enabled_sts_hosts_.erase(j);
            DirtyNotify();
            continue;
        }

        // If this is the most specific STS match, add it to the result. Note: a STS
        // entry at a more specific domain overrides a less specific domain whether
        // or not |include_subdomains| is set.
        if (current_time <= j->second.expiry) {
            if (i == 0 || j->second.include_subdomains) {
                *result = j->second;
                result->domain = DNSDomainToString(host_sub_chunk);
                return true;
            }

            break;
        }
    }

    return false;
}

bool TransportSecurityState::GetDynamicPKPState(const std::string& host,
    PKPState* result)
{
    DCHECK(CalledOnValidThread());

    const std::string canonicalized_host = CanonicalizeHost(host);
    if (canonicalized_host.empty())
        return false;

    base::Time current_time(base::Time::Now());

    for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) {
        std::string host_sub_chunk(&canonicalized_host[i],
            canonicalized_host.size() - i);
        PKPStateMap::iterator j = enabled_pkp_hosts_.find(HashHost(host_sub_chunk));
        if (j == enabled_pkp_hosts_.end())
            continue;

        // If the entry is invalid, drop it.
        if (current_time > j->second.expiry) {
            enabled_pkp_hosts_.erase(j);
            DirtyNotify();
            continue;
        }

        // If this is the most specific PKP match, add it to the result. Note: a PKP
        // entry at a more specific domain overrides a less specific domain whether
        // or not |include_subdomains| is set.
        if (current_time <= j->second.expiry) {
            if (i == 0 || j->second.include_subdomains) {
                *result = j->second;
                result->domain = DNSDomainToString(host_sub_chunk);
                return true;
            }

            break;
        }
    }

    return false;
}

void TransportSecurityState::AddOrUpdateEnabledSTSHosts(
    const std::string& hashed_host,
    const STSState& state)
{
    DCHECK(CalledOnValidThread());
    DCHECK(state.ShouldUpgradeToSSL());
    enabled_sts_hosts_[hashed_host] = state;
}

void TransportSecurityState::AddOrUpdateEnabledPKPHosts(
    const std::string& hashed_host,
    const PKPState& state)
{
    DCHECK(CalledOnValidThread());
    DCHECK(state.HasPublicKeyPins());
    enabled_pkp_hosts_[hashed_host] = state;
}

TransportSecurityState::STSState::STSState()
    : upgrade_mode(MODE_DEFAULT)
    , include_subdomains(false)
{
}

TransportSecurityState::STSState::~STSState()
{
}

bool TransportSecurityState::STSState::ShouldUpgradeToSSL() const
{
    return upgrade_mode == MODE_FORCE_HTTPS;
}

TransportSecurityState::STSStateIterator::STSStateIterator(
    const TransportSecurityState& state)
    : iterator_(state.enabled_sts_hosts_.begin())
    , end_(state.enabled_sts_hosts_.end())
{
}

TransportSecurityState::STSStateIterator::~STSStateIterator()
{
}

TransportSecurityState::PKPState::PKPState()
    : include_subdomains(false)
{
}

TransportSecurityState::PKPState::PKPState(const PKPState& other) = default;

TransportSecurityState::PKPState::~PKPState()
{
}

TransportSecurityState::ExpectCTState::ExpectCTState() { }

TransportSecurityState::ExpectCTState::~ExpectCTState() { }

TransportSecurityState::ExpectStapleState::ExpectStapleState()
    : include_subdomains(false)
{
}

TransportSecurityState::ExpectStapleState::~ExpectStapleState() { }

bool TransportSecurityState::PKPState::CheckPublicKeyPins(
    const HashValueVector& hashes,
    std::string* failure_log) const
{
    // Validate that hashes is not empty. By the time this code is called (in
    // production), that should never happen, but it's good to be defensive.
    // And, hashes *can* be empty in some test scenarios.
    if (hashes.empty()) {
        failure_log->append(
            "Rejecting empty public key chain for public-key-pinned domains: " + domain);
        return false;
    }

    if (HashesIntersect(bad_spki_hashes, hashes)) {
        failure_log->append("Rejecting public key chain for domain " + domain + ". Validated chain: " + HashesToBase64String(hashes) + ", matches one or more bad hashes: " + HashesToBase64String(bad_spki_hashes));
        return false;
    }

    // If there are no pins, then any valid chain is acceptable.
    if (spki_hashes.empty())
        return true;

    if (HashesIntersect(spki_hashes, hashes)) {
        return true;
    }

    failure_log->append("Rejecting public key chain for domain " + domain + ". Validated chain: " + HashesToBase64String(hashes) + ", expected: " + HashesToBase64String(spki_hashes));
    return false;
}

bool TransportSecurityState::PKPState::HasPublicKeyPins() const
{
    return spki_hashes.size() > 0 || bad_spki_hashes.size() > 0;
}

TransportSecurityState::PKPStateIterator::PKPStateIterator(
    const TransportSecurityState& state)
    : iterator_(state.enabled_pkp_hosts_.begin())
    , end_(state.enabled_pkp_hosts_.end())
{
}

TransportSecurityState::PKPStateIterator::~PKPStateIterator()
{
}

} // namespace
